package pl.ydp.components.mathcell.mathrecognizer.model
{
	import pl.ydp.components.mathcell.mathrecognizer.characters.Character;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.MathShape;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Primitive;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.PrimitiveType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.features.LoopTrailData;
	import pl.ydp.components.mathcell.mathrecognizer.model.presets.PrimitivePresets;
	
	public class ShapeModel
	{
		//  ---------- CONSTRUCTOR ---------- 
		
		public function ShapeModel(_char:Number, _prims:Array, _refs:Array, _rels:Array, _relsLogics:Array,
			 _name:String = "", _sophisticated:Boolean = false, _onlyIndependent:Boolean = false /* do not take children into account */)
		{
			character = new Character(String.fromCharCode(_char));
			primitivesType = _prims;
			referencePrimitives = _refs;
			relations = _rels;
			name = _name;
			sophisticated = _sophisticated;
			onlyIndependent = _onlyIndependent;
		
			if (!sophisticated){
				var linesCount:Number = 0;
				var curvesCount:Number = 0;
				for each (var primtype:Number in primitivesType){
					if (primtype & PrimitiveType.LINE){
						linesCount++;
					} else if (primtype & PrimitiveType.CURVE){
						curvesCount++;
					}
				}
				
				if (linesCount >= 2  &&  curvesCount >= 1){
					sophisticated = true;
				}
			}			
		}
		
		//  ---------- PUBLIC FIELDS ---------- 
		
		public var character:Character;
		public var primitivesType:Array;
		public var referencePrimitives:Array;
		public var relations:Array;
		public var name:String;
		public var sophisticated:Boolean;
		public var onlyIndependent:Boolean;

		//public var relationsLogics:Array;
		
		//  ---------- PRIVATE FIELDS ---------- 
		
		private const CHILDREN_OFFSET:Number = 100000;
		
		//  ---------- PUBLIC METHODS ---------- 
		
		public function compare(shape:MathShape):Number{
			
			if (this.name == "MB_≈_STD"){
				var asd:Number = 12;
			}			
			var relationResultsData:Array = new Array();
			var currRelationResultsData:Array = new Array();
			var maxSetResult:Number = 0;
			var nonesCount:Number;
			var result:Number;
			
			// number PRIMITIVES in field used, 
			// if CHILDREN were used, all CHILDREN from the parent PRIMITIVE are counted
			var currPrimitivesInFieldCount:Number;  
			var primitivesInFieldCount:Number; 
			var primitiveChildrenAlreadyCounted:Array;
			var tmpPrimitive:Primitive;
			
			// DO NOT count DOTS when there is sth else than DOT
			var uselessDotsInFieldCount:Number = 0;
			var onlyDots:Boolean = true;
			
			for each (tmpPrimitive in shape.primitives){
				if (tmpPrimitive.type != PrimitiveType.DOT){
					for each (tmpPrimitive in shape.primitives){
						if (tmpPrimitive.type == PrimitiveType.DOT){
							uselessDotsInFieldCount++ ;
						}
					}
					onlyDots = false;
					break;
				}
			}
			
			primitivesInFieldCount = shape.primitives.length;
			
			var primitives:Array;
			
			var maxResultPrimitivesSet:Array;
			
			var primitivesCandidates:Array = findPrimitvesCandidates(shape.primitives);
			
			var primitivesSets:Array = permutateArray(primitivesCandidates);
			
			primitivesSets = removeParentDuplicatedPrimitivesSets(primitivesSets, createParentsReferenceArray(shape.primitives), shape.primitives.length);
			
			var primitivesSetResults:Array = new Array();
			
			for each (var currPrimitivesSet:Array in primitivesSets){
				
				// create array of Primitive's and PrimitiveModel's to proceed comparison
				primitives = new Array();
				
				// reset PRIMITIVES used in field array
				primitiveChildrenAlreadyCounted = new Array;
				for each (tmpPrimitive in shape.primitives){
					primitiveChildrenAlreadyCounted.push(false);
				}
				
				nonesCount = 0;
				result = 0;
				currPrimitivesInFieldCount = shape.primitives.length - uselessDotsInFieldCount;
				
				
				var relationsTotal:Number = 0;
				var relationsSuccessful:Number = 0;
				
				var candidate:PrimitiveCandidate;
				var candidateParented:PrimitiveCandidateParented;
			
				for each (var rp:Object in referencePrimitives){
					
					if (rp is Number){
						
						if (currPrimitivesSet[rp] != PrimitivePresets.NONE){
							
							// find CANDIDATE data
							
							candidate = getPrimitiveCandidateAtIndex(shape.primitives, 	currPrimitivesSet[rp]);
							
							if (candidate is PrimitiveCandidateParented){
								
								candidateParented = candidate as PrimitiveCandidateParented;
								
								// add PRIMITIVE
								
								primitives.push(shape.primitives[candidateParented.index].children[candidateParented.childIndex]);
								
								// update PRIMITIVES used in field array
								
								if (!primitiveChildrenAlreadyCounted[candidateParented.index]){
									
									currPrimitivesInFieldCount -= 1;
									currPrimitivesInFieldCount += shape.primitives[candidateParented.index].children.length;
									primitiveChildrenAlreadyCounted[candidateParented.index] = true;
									
								}
								
							} else {
								
								primitives.push(shape.primitives[candidate.index]);
								
								if (!onlyDots  &&  shape.primitives[candidate.index].type == PrimitiveType.DOT){
									uselessDotsInFieldCount --;
									currPrimitivesInFieldCount ++;
								}
								
								
							}
							
							//primitives.push(shape.primitives[currPrimitivesSet[rp]]);	
						} else {
							
							primitives.push(PrimitivePresets.NONE);
							nonesCount++;
							
						}
						
					} else if (rp is PrimitiveModel){
						
						primitives.push(rp.cloneResized(shape.field));
						
					}
					
				} //  end for each( ... in referencePrimitives)
				
				if (nonesCount < 2){
					
				// verify all stored relations with current primitives set

					currRelationResultsData = new Array();
					
					for (var rel:Number = 0 ; rel < relations.length ; rel ++ ){
						relationsTotal += relations[rel].relation.weight;
						
						var ript:Number = primitives[relations[rel].refIndex];
						var tipt:Number = primitives[relations[rel].testIndex];
						
						if (primitives[relations[rel].refIndex] == PrimitivePresets.NONE  ||  primitives[relations[rel].testIndex] == PrimitivePresets.NONE){
							currRelationResultsData.push(0);
							continue;
						}
						if (relations[rel].refIndex >= primitives.length  ||  relations[rel].testIndex >= primitives.length){
							currRelationResultsData.push(0);
							continue;
						}
						
						if (relations[rel].relation.check(primitives[relations[rel].refIndex], primitives[relations[rel].testIndex])){
							relationsSuccessful += relations[rel].relation.weight;
							currRelationResultsData.push(relations[rel].relation.weight);
						} else {
							currRelationResultsData.push(0);
						}
						 
					}
					
					result = relationsSuccessful / relationsTotal;
										
					if (nonesCount){
						result = result * (primitivesType.length - nonesCount) / primitivesType.length;
					}
					
					
					//trace(this.name);
					//trace(currRelationResultsData);
					//trace(result);
				}
				
				if (result > maxSetResult){
					maxSetResult = result;
					relationResultsData = currRelationResultsData;
					primitivesInFieldCount = currPrimitivesInFieldCount;
					maxResultPrimitivesSet = currPrimitivesSet;
				}
				
				primitivesSetResults.push(result);
			
			} // end for each currPrimitivesSet
			
			// remove unused LOOP TRAILS from IN FIELD counter
			if (primitivesInFieldCount != primitivesType.length  &&  shape.features.loopTails.length > 0){
				
				var trailsUsed:Number = 0;
				
				for each (var ltd:LoopTrailData in shape.features.loopTails){
					for each (var currIndex:Number in maxResultPrimitivesSet){
						if (currIndex == ltd.lineIndex){
							trailsUsed ++;
							break;
						}
					}
				} 
				
				primitivesInFieldCount -= (shape.features.loopTails.length - trailsUsed);
			}
			
			
			var max:Number = maxSetResult;
			
			if (primitivesInFieldCount != primitivesType.length  &&  
				(!sophisticated  ||  (sophisticated  &&  primitivesInFieldCount >= primitivesType.length+2))){
				var discount:Number = 1;
				if (primitivesInFieldCount > primitivesType.length){
					discount = primitivesType.length / primitivesInFieldCount;
				} else {
					discount = 1;
				}
				max *= discount;
				
			}
			if (max > 0){
				//trace(max);
			}
			
			return max;
		}
		
		//  -------------------- PRIVATE METHODS -------------------- 
		
		///
		/// Returns array of candidates.
		/// 
		/// ex. primitivesCandidates[0] is an array of primitives indexes from shape.primitives for 0th primitive in model.
		/// primitivesCandidates[0] == {1,3,7} means that shape's primitives indexed 1, 3 and 7 can be equivalent of 0th primitive in model.
		/// It is next step to find the best match of those candidates.
		///
		private function findPrimitvesCandidates(testPrimitives:Array):Array{
			
			if (!testPrimitives[0] is Primitive){
				return new Array();
			}
			
			var r:Number;
			var t:Number;
			var tc:Number
			
			var primitivesCandidates:Array = new Array();
			
			for (r = 0 ; r < primitivesType.length ; r ++){
				
				primitivesCandidates.push( new Array );
				
				for (t = 0 ; t < testPrimitives.length ; t ++){
					
					if (primitivesType[r] & testPrimitives[t].type & PrimitiveType.CURVE){
						var a:Number = (primitivesType[r] & testPrimitives[t].type) - primitivesType[r];
					}
					
					var cmp:Number = primitivesType[t] & testPrimitives[t].type & PrimitiveType.LINE;
					var tvr:Number = primitivesType[r] & testPrimitives[t].type -  PrimitiveType.LINE;
					var typeVSprimitiveType:Number = (primitivesType[r] & testPrimitives[t].type);
					var typeCurevemasked:Number = (primitivesType[r] & PrimitiveType.CURVE_MASK);
					
					// check "first line" primitives
					
					if (primitivesType[r] == testPrimitives[t].type  
						||  
						(primitivesType[r] & testPrimitives[t].type & PrimitiveType.WAVE)
						||  
						((primitivesType[r] & testPrimitives[t].type & PrimitiveType.LINE)  &&  ((primitivesType[r] & testPrimitives[t].type) > PrimitiveType.LINE)) 
						||
						((primitivesType[r] & testPrimitives[t].type & PrimitiveType.CURVE) && ((primitivesType[r] & testPrimitives[t].type) == (primitivesType[r] & PrimitiveType.CURVE_MASK)))  ){
						
						//primitivesCandidates[r].push(t);
						primitivesCandidates[r].push(t);
						
					} else if (testPrimitives[t].children.length > 0  &&  onlyIndependent == false){
						
						// check "child" primitives
						
						for (tc = 0 ; tc < testPrimitives[t].children.length ; tc ++){
							
							if (primitivesType[r] == testPrimitives[t].children[tc].type  
								||  
								((primitivesType[r] & testPrimitives[t].children[tc].type & PrimitiveType.LINE)  &&  ((primitivesType[r] & testPrimitives[t].children[tc].type) -  PrimitiveType.LINE))
								) {
								
								//primitivesCandidates[r].push(Parameters.CANDIDATE_IS_CHILDREN | t*Parameters.CANDIDATE_CHILDREN_OFFSET | tc);
								primitivesCandidates[r].push(findPrimitiveCandidateIndex(testPrimitives, new PrimitiveCandidateParented(tc,t)));
								
							} 
							
						}
						
					}
					
				} // end for (var t...
				
				if (primitivesCandidates[r].length == 0){
					primitivesCandidates[r].push(PrimitivePresets.NONE);
				}
				
			} // end for (var r...
						
			return primitivesCandidates;
		}
		
		
		
		
		//------------------------------------------- 
		
		
		public function permutateArray(input:Array, used:Array = null):Array{
			
			var output:Array = new Array();
			var first:Boolean = false;
			var lastUsed:Boolean = false;
				
			if (used == null){
				first = true;
				var max:Number = 0;
				for each (var smallArr:Array in input){
					for each (var item:Number in smallArr){
						if (item > max){
							max = item;
						}	
					}
					 
				}
				used = new Array(max+1);
				for (var i:Number = 0 ; i < used.length ; i ++ ){
					used[i] = false;
				}
			}
			
			var arx:Array;
			
			for (var el:Number = 0 ; el < input[0].length ; el ++ ){
				if (!used[input[0][el]]){
					
					if (input.length > 1){
						
						var usedNew:Array = new Array();
						
						for each( var b:Boolean in used){
							usedNew.push(b);
						}
						
						usedNew[input[0][el]] = true;
						
						arx = permutateArray(input.slice(1), usedNew);
						
						for each (var mArx:Array in arx){
							mArx.unshift(input[0][el]);	
							output.push(mArx);
						}
						
						
					} else { // input.length == 1
						arx = new Array();
						arx.push(input[0][el]);
						output.push(arx);
						
												
					}
					
					
				}  // end if !used
				
				
			} // end for
			
			return output;
		}
		
		//------------------------------------------- 
		
		private function createParentsReferenceArray(primitives:Array):Array{
			
			var indexes:Array = new Array;
			var prim:Primitive;
			var j:Number;
			var k:Number;
			var counter:Number = 0;
			
			for (k = 0 ; k < primitives.length ; k ++){
				indexes.push(counter);
				counter++ ;
			}			
			
			for (j = 0 ; j < primitives.length ; j ++){
				if (primitives[j].children.length > 0){
					for (k = 0 ; k < primitives[j].children.length ; k ++){		
						indexes.push(j);
						counter++ ;		
					}
				}
			}	
			
			return indexes;
			
		}
		
		//------------------------------------------- 
		
		private function findPrimitiveCandidateIndex(primitives:Array, candidate:PrimitiveCandidate):Number{
			
			if (!(candidate is PrimitiveCandidateParented)){
				return candidate.index;
			} else {
				
				var j:Number;
				var k:Number;
				var childrenCount:Number = 0;
				
				var candidateParented:PrimitiveCandidateParented = candidate as PrimitiveCandidateParented;
				
				for (j = 0 ; j < primitives.length ; j ++ ){
					
					for (k = 0 ; k < primitives[j].children.length ; k ++ ){
						
						if (candidateParented.index == k  && candidateParented.childIndex == j){
							return primitives.length + childrenCount;
						}
						
						childrenCount++;
						
					}
					
				}
				
			}
			
			return -1;
			
		}
		
		//------------------------------------------- 
		
		
		private function getPrimitiveCandidateAtIndex(testPrimitives:Array, index:Number):PrimitiveCandidate{
			
			if (index < testPrimitives.length){
				return new PrimitiveCandidate(index);
			} else {
				
				var childIndex:Number = index - testPrimitives.length;
				var childrenCount:Number = 0;
				var k:Number;
				
				for (k = 0 ; k < testPrimitives.length ; k ++){
					
					if (testPrimitives[k].children.length + childrenCount - 1 >= childIndex){
						return new PrimitiveCandidateParented(k, childIndex - childrenCount);
					} else {
						childrenCount += testPrimitives[k].children.length;
					}
					
				}
				
			}
			
			return new PrimitiveCandidate(-1);
						
		}
		
		//------------------------------------------- 
		
		private function removeParentDuplicatedPrimitivesSets(primitivesSets:Array, parentReferences:Array, parentsCount:Number):Array{
			
			var cleanPrimitivesSets:Array = new Array;
			var parentsUsed:Array;
			
			var k:Number;
			var currPrimitiveSet:Array;
			var currPrimitiveIndex:Number;
			var passed:Boolean;
			
			for each (currPrimitiveSet in primitivesSets){
				parentsUsed = new Array;
				for (k = 0 ; k < parentsCount ; k ++){
					parentsUsed.push(false);
				}
				
				passed = true;
				
				for each(currPrimitiveIndex in currPrimitiveSet){
					if (currPrimitiveIndex < parentsCount){
						parentsUsed[parentReferences[currPrimitiveIndex]] = true;
					}
				}
				
				for each(currPrimitiveIndex in currPrimitiveSet){
					if (currPrimitiveIndex >= parentsCount){
						if (parentsUsed[parentReferences[currPrimitiveIndex]] == true){
							passed = false;
							break;
						}
					}
				}	
				
				if (passed){
					cleanPrimitivesSets.push(currPrimitiveSet);
				}
			}
			
			return cleanPrimitivesSets;
			
		}
		
		//------------------------------------------- 
		
		private function relationResult(relation:PrimitivesRelation, refPrimitive:PrimitiveModel, testPrimitive:PrimitiveModel):Boolean{
			
			return false;

		}
		
		
	}
}